﻿using UnityEngine;
using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;

namespace Hont.AStar
{
    public class HontAStarUnityHelper
    {
        static GameObject mAstarDebugRoot;

        public static Vector3[] CombinePath(Vector3[] positionArr)
        {
            if (positionArr.Length < 2) return positionArr;

            var resultList = new HashSet<Vector3>();
            var mOptimizablePathList = new List<Vector3>();
            Vector3? lastDir = null;

            resultList.Add(positionArr.First());

            for (int i = 1; i < positionArr.Length; i++)
            {
                var x = positionArr[i - 1];
                var y = positionArr[i];
                var flag = true;

                var dir = (y - x).normalized;

                if (lastDir != null && (!Mathf.Approximately(dir.x, lastDir.Value.x) || !Mathf.Approximately(dir.y, lastDir.Value.y) || !Mathf.Approximately(dir.z, lastDir.Value.z)))
                    flag = false;

                mOptimizablePathList.Add(x);

                if (!flag)
                {
                    resultList.Add(mOptimizablePathList.LastOrDefault());
                    mOptimizablePathList.Clear();
                }

                lastDir = dir;
            }

            resultList.Add(positionArr.Last());

            return resultList.ToArray();
        }

        public static Vector3 MatchToWalkableNearestPoint(HontAStarUnity astar, Vector3 position)
        {
            if (astar.OctTree == null)
                throw new NotSupportedException("Not support Non-'OCT Tree' astar pathfinding mode!");

            var items = astar.OctTree.Root.GetItems(m =>
            {
                var result = -Vector3.Distance(m.Bounds.center, position);

                if ((bool)m.UserDataDict[HontAStarUnity.OCTTREEUSERDATA_HAS_WALKABLE_BOX] == false)
                    result = float.MinValue;

                return result;
            });

            if (items == null || items.Length == 0)
                return position;

            Array.Sort(items, (x, y) =>
            {
                var a = (x.Position - position).sqrMagnitude.CompareTo((y.Position - position).sqrMagnitude);
                var b = astar.Grid.GetIsWalkable(x.Value).CompareTo(astar.Grid.GetIsWalkable(y.Value));
                return a + (-b) * 2;
            });

            return items[0].Position;
        }

        public static Bounds BuildBounds(Vector3[] points)
        {
            var xMin = points.Min(m => m.x);
            var yMin = points.Min(m => m.y);
            var zMin = points.Min(m => m.z);
            var xMax = points.Max(m => m.x);
            var yMax = points.Max(m => m.y);
            var zMax = points.Max(m => m.z);

            var min = new Vector3(xMin, yMin, zMin);
            var max = new Vector3(xMax, yMax, zMax);

            return new Bounds() { min = min, max = max };
        }

        public static Bounds CombineBounds(Bounds[] boundsArr)
        {
            if (boundsArr.Length == 0) return default(Bounds);
            if (boundsArr.Length == 1) return boundsArr[0];

            var points = boundsArr.SelectMany(m => new Vector3[] { m.min, m.max });
            return BuildBounds(points.ToArray());
        }

        public static IEnumerator EasyPathMove(Transform seeker, Vector3[] pathArr, float speed = 30f, float error = 0.1f)
        {
            for (int i = 0; i < pathArr.Length; i++)
            {
                var point = pathArr[i];

                while (true)
                {
                    if (Vector3.Distance(seeker.transform.position, point) < error)
                        break;

                    seeker.transform.position = Vector3.MoveTowards(seeker.transform.position, point, speed * Time.deltaTime);
                    yield return new WaitForEndOfFrame();
                }
            }
        }

        public static void DebugPathfindingPath(Vector3[] pathArr, float duration = 1f)
        {
            var go = CreatePathfindingPathDebuger(pathArr);

            UnityEngine.Object.Destroy(go, duration);
        }

        public static HontAStarUnityPathPointDebuger CreatePathfindingPathDebuger(Vector3[] pathArr)
        {
            if (!mAstarDebugRoot)
                mAstarDebugRoot = new GameObject("[AStar Debug]");

            var go = new GameObject("Pathfinding Debug Point" + Time.time);
            go.transform.parent = mAstarDebugRoot.transform;

            var pathPoint = go.AddComponent<HontAStarUnityPathPointDebuger>();
            pathPoint.pathArr = pathArr;

            return pathPoint;
        }
    }
}
